﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using cfg.character;
using kemocard.Scripts.Buff;
using kemocard.Scripts.Common;
using kemocard.Scripts.Module.Battle;
using Newtonsoft.Json;
using Vector2 = Godot.Vector2;

namespace kemocard.Scripts.Pawn;

[JsonObject(MemberSerialization.OptIn)]
public class BasePawn : IDisposable
{
    [JsonProperty] public string Id = "";
    protected float BaseHealth;
    protected float BasePDefense;
    protected float BaseMDefense;
    protected float BasePAttack;
    protected float BaseMAttack;
    public int Attribute = (int)cfg.pawn.Attribute.NONE;
    public string Name = "";
    public string Icon = "";
    public string Description = "";
    public string ImagePath = "";
    public int Race = (int)cfg.pawn.Race.NONE;
    public Role Role = Role.NORMAL;

    [JsonProperty] protected readonly Dictionary<string, BaseBuff> Buffs = [];

    // 只是给怪物用的
    public Vector2 Position;

    [OnDeserialized]
    internal void InitAfterDeserialized(StreamingContext context)
    {
        InitFromConfig(Id, true);
    }

    public virtual bool InitFromConfig(string configId, bool fromSave = false)
    {
        var conf = GameCore.Tables.TbPawnBaseProp.GetOrDefault(configId);
        if (conf == null) return false;
        BaseHealth = conf.BaseHealth;
        BasePDefense = conf.BasePDefense;
        BaseMDefense = conf.BaseMDefense;
        BasePAttack = conf.BasePAttack;
        BaseMAttack = conf.BaseMAttack;
        Attribute = (int)conf.Attribute;
        Role = conf.Role;
        Name = conf.Name;
        Icon = conf.Icon;
        Description = conf.Description;
        ImagePath = conf.ImagePath;
        Id = conf.Id;
        Race = (int)conf.Race;
        RefreshProps();
        return true;
    }

    public void AddBuff(BaseBuff newBaseBuff)
    {
        if (newBaseBuff == null) return;
        if (Buffs.TryGetValue(newBaseBuff.Id, out var buff))
        {
            if (buff.IsUnique && newBaseBuff.IsUnique)
            {
                if (buff.HashCode == newBaseBuff.HashCode)
                {
                    newBaseBuff.HashCode = StaticUtil.GetRandomHashCode();
                }

                newBaseBuff.Owner = this;
                // 如果有独特的重复buff，则用它的hashcode作为字典的键
                Buffs.Add(newBaseBuff.HashCode, newBaseBuff);

                return;
            }

            buff.Duration = newBaseBuff.Duration;

            if (buff.StackLimit > 0 && buff.StackNum + 1 > buff.StackLimit)
            {
                return;
            }

            buff.StackNum++;
        }
        else
        {
            newBaseBuff.Owner = this;
            Buffs.Add(newBaseBuff.Id, newBaseBuff);
        }

        newBaseBuff.AfterSettingOwner();
        RefreshProps();
    }

    public virtual int MaxHealth { get; set; }
    public int PDefense;
    public int MDefense;
    public int PAttack;
    public int MAttack;
    public float CriticalChance;
    public float CriticalRate = 1.5f;

    /** 嘲讽优先级 */
    public int Taunt;

    /** 伤害减免 */
    public float Reduction = 1f;

    public virtual void RefreshProps()
    {
        var oldMaxHealth = MaxHealth;
        var oldPAttack = PAttack;
        var oldMAttack = MAttack;
        var oldPDefense = PDefense;
        var oldMDefense = MDefense;
        var oldTaunt = Taunt;
        var oldReduction = Reduction;
        var oldCriticalChance = CriticalChance;
        var oldCriticalRate = CriticalRate;
        TempPropStruct tempProp = new();
        foreach (var buff in Buffs)
        {
            for (var i = 0; i < buff.Value.StackNum; i++)
            {
                buff.Value.RefreshProps(inStruct: ref tempProp);
            }
        }

        MaxHealth = Math.Max(1, (int)((BaseHealth + tempProp.AddHealth) * (1 + tempProp.ExtraHealth)));
        PAttack = Math.Max(0, (int)((BasePAttack + tempProp.AddPAttack) * (1 + tempProp.ExtraPAttack)));
        MAttack = Math.Max(0, (int)((BaseMAttack + tempProp.AddMAttack) * (1 + tempProp.ExtraMAttack)));
        PDefense = Math.Max(0, (int)((BasePDefense + tempProp.AddPDefense) * (1 + tempProp.ExtraPDefense)));
        MDefense = Math.Max(0, (int)((BaseMDefense + tempProp.AddMDefense) * (1 + tempProp.ExtraMDefense)));
        Taunt = tempProp.Taunt;
        Reduction = Math.Max(0, tempProp.Reduction);
        CriticalChance = tempProp.AddCriticalChance;
        CriticalRate = 1.5f + tempProp.AddCriticalRate;

        if (oldMaxHealth != MaxHealth || oldPAttack != PAttack || oldMAttack != MAttack || oldPDefense != PDefense ||
            oldMDefense != MDefense || oldTaunt != Taunt || Math.Abs(oldReduction - Reduction) > 0.01f
            || Math.Abs(oldCriticalChance - CriticalChance) > 0.01f ||
            Math.Abs(oldCriticalRate - CriticalRate) > 0.01f)
        {
            GameCore.EventBus.PostEvent(CommonEvent.PlayerPropUpdate, this);
        }
    }

    public virtual Task OnAttacked(ref Damage damage)
    {
        if (this is IBattlePawn { IsDead: true })
        {
            return Task.CompletedTask;
        }

        object data = damage;
        List<Task> tasks = [];
        tasks.AddRange(Buffs.Where(keyValuePair => keyValuePair.Value.Tags.Contains(BuffTag.Attacked))
            .Select(buff => buff.Value.ApplyBuff(ref data, BuffTag.Attacked)));

        return Task.WhenAll(tasks);
    }

    public virtual Task OnAttack(ref Damage damage)
    {
        if (this is IBattlePawn { IsDead: true })
        {
            return Task.CompletedTask;
        }

        object data = damage;
        List<Task> tasks = [];
        tasks.AddRange(Buffs.Where(keyValuePair => keyValuePair.Value.Tags.Contains(BuffTag.Attack))
            .Select(buff => buff.Value.ApplyBuff(ref data, BuffTag.Attack)));
        return Task.WhenAll(tasks);
    }

    public virtual Task OnHeal(ref HealStruct heal)
    {
        if (this is IBattlePawn { IsDead: true })
        {
            return Task.CompletedTask;
        }

        object data = heal;
        List<Task> tasks = [];
        tasks.AddRange(Buffs.Where(buff => buff.Value.Tags.Contains(BuffTag.Heal))
            .Select(buff => buff.Value.ApplyBuff(ref data, BuffTag.Heal)));
        return Task.WhenAll(tasks);
    }

    public virtual Task OnHealed(HealStruct heal)
    {
        if (this is IBattlePawn { IsDead: true })
        {
            return Task.CompletedTask;
        }

        object data = heal;
        List<Task> tasks = [];
        tasks.AddRange(Buffs.Where(buff => buff.Value.Tags.Contains(BuffTag.Heal))
            .Select(buff => buff.Value.ApplyBuff(ref data, BuffTag.Healed)));

        return Task.WhenAll(tasks);
    }

    public virtual Task ExecuteBuffs(ref object data, BuffTag tag = BuffTag.None)
    {
        if (this is IBattlePawn { IsDead: true })
        {
            return Task.CompletedTask;
        }

        List<Task> tasks = [];
        // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
        foreach (var keyValuePair in Buffs)
        {
            tasks.Add(keyValuePair.Value?.ApplyBuff(ref data, tag));
        }

        return Task.WhenAll(tasks);
    }

    public virtual void RemoveBuff(string buffId, bool removeAll = false, int removeStack = 1)
    {
        if (!Buffs.TryGetValue(buffId, out var buff)) return;
        if (!removeAll)
        {
            buff.StackNum -= removeStack;
            if (buff.StackNum > 0) return;
        }

        buff.OnRemoved();
        Buffs.Remove(buffId);
    }

    public void FindBuffById(string buffId, out BaseBuff buff)
    {
        buff = Buffs.GetValueOrDefault(buffId);
    }

    public void CountAllBuff()
    {
        foreach (var buff in Buffs.Values)
        {
            buff.CountDuration();
        }
    }

    public virtual void Dispose()
    {
        foreach (var keyValuePair in Buffs)
        {
            keyValuePair.Value.Owner = null;
            keyValuePair.Value.Causer = null;
            GameCore.EventBus.RemoveObjAllEvents(keyValuePair.Value);
            // keyValuePair.Value.Dispose();
        }
    }
}

public struct TempPropStruct
{
    public float AddHealth = 0;
    public float ExtraHealth = 0;
    public float AddPAttack = 0;
    public float ExtraPAttack = 0;
    public float AddMAttack = 0;
    public float ExtraMAttack = 0;
    public float AddPDefense = 0;
    public float ExtraPDefense = 0;
    public float AddMDefense = 0;
    public float ExtraMDefense = 0;
    public float AddHeal = 0;
    public float ExtraHeal = 0;
    public int AddDraw = 0;
    public int Taunt = 0;
    public float Reduction = 1f;
    public float AddCriticalChance = 0f;
    public float AddCriticalRate = 0f;

    public TempPropStruct()
    {
    }
}